Poznaj architektur臋 heksagonaln膮 i czyst膮 architektur臋 do budowy 艂atwych w utrzymaniu, skalowalnych i testowalnych aplikacji frontendowych. Naucz si臋 ich zasad, korzy艣ci i praktycznych strategii implementacji.
Architektura frontendu: Architektura heksagonalna i czysta architektura dla skalowalnych aplikacji
W miar臋 wzrostu z艂o偶ono艣ci aplikacji frontendowych, dobrze zdefiniowana architektura staje si臋 kluczowa dla utrzymania, testowalno艣ci i skalowalno艣ci. Dwa popularne wzorce architektoniczne, kt贸re odpowiadaj膮 na te potrzeby, to architektura heksagonalna (znana r贸wnie偶 jako porty i adaptery) oraz czysta architektura. Chocia偶 wywodz膮 si臋 one ze 艣wiata backendu, ich zasady mo偶na skutecznie zastosowa膰 w tworzeniu frontendu, aby tworzy膰 solidne i elastyczne interfejsy u偶ytkownika.
Czym jest architektura frontendu?
Architektura frontendu definiuje struktur臋, organizacj臋 i interakcje r贸偶nych komponent贸w w aplikacji frontendowej. Stanowi ona plan, wed艂ug kt贸rego aplikacja jest budowana, utrzymywana i skalowana. Dobra architektura frontendu promuje:
- 艁atwo艣膰 utrzymania: 艁atwiejsze zrozumienie, modyfikacja i debugowanie kodu.
- Testowalno艣膰: U艂atwia pisanie test贸w jednostkowych i integracyjnych.
- Skalowalno艣膰: Pozwala aplikacji radzi膰 sobie z rosn膮c膮 z艂o偶ono艣ci膮 i obci膮偶eniem u偶ytkownik贸w.
- Wielokrotne u偶ycie: Promuje ponowne wykorzystanie kodu w r贸偶nych cz臋艣ciach aplikacji.
- Elastyczno艣膰: Dostosowuje si臋 do zmieniaj膮cych si臋 wymaga艅 i nowych technologii.
Bez jasnej architektury projekty frontendowe mog膮 szybko sta膰 si臋 monolityczne i trudne w zarz膮dzaniu, co prowadzi do zwi臋kszonych koszt贸w rozwoju i zmniejszonej zwinno艣ci.
Wprowadzenie do architektury heksagonalnej
Architektura heksagonalna, zaproponowana przez Alistaira Cockburna, ma na celu oddzielenie kluczowej logiki biznesowej aplikacji od zewn臋trznych zale偶no艣ci, takich jak bazy danych, frameworki UI i interfejsy API firm trzecich. Osi膮ga to poprzez koncepcj臋 port贸w i adapter贸w.
Kluczowe koncepcje architektury heksagonalnej:
- Rdze艅 (Domena): Zawiera logik臋 biznesow膮 i przypadki u偶ycia aplikacji. Jest niezale偶ny od wszelkich zewn臋trznych framework贸w czy technologii.
- Porty: Interfejsy definiuj膮ce, w jaki spos贸b rdze艅 komunikuje si臋 ze 艣wiatem zewn臋trznym. Reprezentuj膮 one granice wej艣cia i wyj艣cia rdzenia.
- Adaptery: Implementacje port贸w, kt贸re 艂膮cz膮 rdze艅 z konkretnymi systemami zewn臋trznymi. Istniej膮 dwa typy adapter贸w:
- Adaptery steruj膮ce (pierwotne): Inicjuj膮 interakcje z rdzeniem. Przyk艂ady obejmuj膮 komponenty UI, interfejsy wiersza polece艅 lub inne aplikacje.
- Adaptery sterowane (wt贸rne): S膮 wywo艂ywane przez rdze艅 w celu interakcji z systemami zewn臋trznymi. Przyk艂ady obejmuj膮 bazy danych, interfejsy API lub systemy plik贸w.
Rdze艅 nie wie nic o konkretnych adapterach. Komunikuje si臋 z nimi wy艂膮cznie za po艣rednictwem port贸w. To oddzielenie pozwala na 艂atw膮 wymian臋 r贸偶nych adapter贸w bez wp艂ywu na logik臋 rdzenia. Na przyk艂ad mo偶na prze艂膮czy膰 si臋 z jednego frameworka UI (np. React) na inny (np. Vue.js), po prostu zast臋puj膮c adapter steruj膮cy.
Zalety architektury heksagonalnej:
- Lepsza testowalno艣膰: Kluczowa logika biznesowa mo偶e by膰 艂atwo testowana w izolacji, bez polegania na zewn臋trznych zale偶no艣ciach. Mo偶na u偶y膰 atrap adapter贸w (mock adapters) do symulacji zachowania system贸w zewn臋trznych.
- Wi臋ksza 艂atwo艣膰 utrzymania: Zmiany w systemach zewn臋trznych maj膮 minimalny wp艂yw na logik臋 rdzenia. U艂atwia to utrzymanie i rozwijanie aplikacji w czasie.
- Wi臋ksza elastyczno艣膰: Mo偶na 艂atwo dostosowa膰 aplikacj臋 do nowych technologii i wymaga艅, dodaj膮c lub zast臋puj膮c adaptery.
- Lepsza mo偶liwo艣膰 ponownego u偶ycia: Kluczowa logika biznesowa mo偶e by膰 ponownie wykorzystana w r贸偶nych kontekstach poprzez pod艂膮czenie jej do r贸偶nych adapter贸w.
Wprowadzenie do czystej architektury
Czysta architektura, spopularyzowana przez Roberta C. Martina (Wujka Boba), to kolejny wzorzec architektoniczny, kt贸ry k艂adzie nacisk na separacj臋 odpowiedzialno艣ci (separation of concerns) i oddzielenie. Skupia si臋 na tworzeniu systemu, kt贸ry jest niezale偶ny od framework贸w, baz danych, interfejsu u偶ytkownika i wszelkich czynnik贸w zewn臋trznych.
Kluczowe koncepcje czystej architektury:
Czysta architektura organizuje aplikacj臋 w koncentryczne warstwy, gdzie najbardziej abstrakcyjny i uniwersalny kod znajduje si臋 w centrum, a najbardziej konkretny i specyficzny technologicznie kod na zewn臋trznych warstwach.
- Encje (Entities): Reprezentuj膮 kluczowe obiekty biznesowe i regu艂y aplikacji. S膮 niezale偶ne od wszelkich system贸w zewn臋trznych.
- Przypadki u偶ycia (Use Cases): Definiuj膮 logik臋 biznesow膮 aplikacji i spos贸b, w jaki u偶ytkownicy wchodz膮 w interakcj臋 z systemem. Orkiestruj膮 one encje w celu wykonania okre艣lonych zada艅.
- Adaptery interfejs贸w (Interface Adapters): Konwertuj膮 dane mi臋dzy przypadkami u偶ycia a systemami zewn臋trznymi. Ta warstwa obejmuje prezentery, kontrolery i bramy (gateways).
- Frameworki i sterowniki (Frameworks and Drivers): Najbardziej zewn臋trzna warstwa, zawieraj膮ca framework UI, baz臋 danych i inne technologie zewn臋trzne.
Zasada zale偶no艣ci w czystej architekturze m贸wi, 偶e warstwy zewn臋trzne mog膮 zale偶e膰 od warstw wewn臋trznych, ale warstwy wewn臋trzne nie mog膮 zale偶e膰 od warstw zewn臋trznych. Zapewnia to, 偶e kluczowa logika biznesowa jest niezale偶na od jakichkolwiek zewn臋trznych framework贸w czy technologii.
Zalety czystej architektury:
- Niezale偶no艣膰 od framework贸w: Architektura nie opiera si臋 na istnieniu jakiej艣 biblioteki oprogramowania bogatej w funkcje. Pozwala to na u偶ywanie framework贸w jako narz臋dzi, zamiast by膰 zmuszonym do umieszczania systemu w ich ograniczonych ramach.
- Testowalno艣膰: Regu艂y biznesowe mo偶na testowa膰 bez interfejsu u偶ytkownika, bazy danych, serwera WWW ani 偶adnego innego elementu zewn臋trznego.
- Niezale偶no艣膰 od UI: Interfejs u偶ytkownika mo偶na 艂atwo zmieni膰, nie zmieniaj膮c reszty systemu. Interfejs WWW mo偶na zast膮pi膰 interfejsem konsolowym, nie zmieniaj膮c 偶adnych regu艂 biznesowych.
- Niezale偶no艣膰 od bazy danych: Mo偶esz zamieni膰 Oracle lub SQL Server na Mongo, BigTable, CouchDB lub co艣 innego. Twoje regu艂y biznesowe nie s膮 powi膮zane z baz膮 danych.
- Niezale偶no艣膰 od jakiegokolwiek czynnika zewn臋trznego: W rzeczywisto艣ci Twoje regu艂y biznesowe po prostu nie wiedz膮 *nic* o 艣wiecie zewn臋trznym.
Zastosowanie architektury heksagonalnej i czystej architektury w tworzeniu frontendu
Chocia偶 architektura heksagonalna i czysta architektura s膮 cz臋sto kojarzone z tworzeniem backendu, ich zasady mo偶na skutecznie zastosowa膰 w aplikacjach frontendowych w celu poprawy ich architektury i 艂atwo艣ci utrzymania. Oto jak:
1. Zidentyfikuj rdze艅 (domen臋)
Pierwszym krokiem jest zidentyfikowanie kluczowej logiki biznesowej Twojej aplikacji frontendowej. Obejmuje to encje, przypadki u偶ycia i regu艂y biznesowe, kt贸re s膮 niezale偶ne od frameworka UI czy jakichkolwiek zewn臋trznych API. Na przyk艂ad w aplikacji e-commerce rdze艅 mo偶e obejmowa膰 logik臋 zarz膮dzania produktami, koszykami na zakupy i zam贸wieniami.
Przyk艂ad: W aplikacji do zarz膮dzania zadaniami, rdze艅 domeny m贸g艂by sk艂ada膰 si臋 z:
- Encje: Zadanie (Task), Projekt (Project), U偶ytkownik (User)
- Przypadki u偶ycia: Utw贸rzZadanie, ZaktualizujZadanie, PrzypiszZadanie, Uko艅czZadanie, Wy艣wietlZadania
- Regu艂y biznesowe: Zadanie musi mie膰 tytu艂, zadanie nie mo偶e by膰 przypisane do u偶ytkownika, kt贸ry nie jest cz艂onkiem projektu.
2. Zdefiniuj porty i adaptery (architektura heksagonalna) lub warstwy (czysta architektura)
Nast臋pnie zdefiniuj porty i adaptery (architektura heksagonalna) lub warstwy (czysta architektura), kt贸re oddzielaj膮 rdze艅 od system贸w zewn臋trznych. W aplikacji frontendowej mog膮 to by膰:
- Komponenty UI (Adaptery steruj膮ce / Frameworki i sterowniki): Komponenty React, Vue.js, Angular, kt贸re wchodz膮 w interakcj臋 z u偶ytkownikiem.
- Klienci API (Adaptery sterowane / Adaptery interfejs贸w): Us艂ugi, kt贸re wykonuj膮 偶膮dania do backendowych API.
- Magazyny danych (Adaptery sterowane / Adaptery interfejs贸w): Local storage, IndexedDB lub inne mechanizmy przechowywania danych.
- Zarz膮dzanie stanem (Adaptery interfejs贸w): Redux, Vuex lub inne biblioteki do zarz膮dzania stanem.
Przyk艂ad z u偶yciem architektury heksagonalnej:
- Rdze艅: Logika zarz膮dzania zadaniami (encje, przypadki u偶ycia, regu艂y biznesowe).
- Porty:
TaskService(definiuje metody tworzenia, aktualizacji i pobierania zada艅). - Adapter steruj膮cy: Komponenty React, kt贸re u偶ywaj膮
TaskServicedo interakcji z rdzeniem. - Adapter sterowany: Klient API, kt贸ry implementuje
TaskServicei wykonuje 偶膮dania do backendowego API.
Przyk艂ad z u偶yciem czystej architektury:
- Encje: Zadanie (Task), Projekt (Project), U偶ytkownik (User) (czyste obiekty JavaScript).
- Przypadki u偶ycia: CreateTaskUseCase, UpdateTaskUseCase (orkiestruj膮 encje).
- Adaptery interfejs贸w:
- Kontrolery: Obs艂uguj膮 dane wej艣ciowe od u偶ytkownika z UI.
- Prezentery: Formatuj膮 dane do wy艣wietlenia w UI.
- Bramy (Gateways): Wchodz膮 w interakcj臋 z klientem API.
- Frameworki i sterowniki: Komponenty React, klient API (axios, fetch).
3. Zaimplementuj adaptery (architektura heksagonalna) lub warstwy (czysta architektura)
Teraz zaimplementuj adaptery lub warstwy, kt贸re 艂膮cz膮 rdze艅 z systemami zewn臋trznymi. Upewnij si臋, 偶e adaptery lub warstwy s膮 niezale偶ne od rdzenia i 偶e rdze艅 komunikuje si臋 z nimi wy艂膮cznie za po艣rednictwem port贸w lub interfejs贸w. Pozwala to na 艂atw膮 wymian臋 r贸偶nych adapter贸w lub warstw bez wp艂ywu na logik臋 rdzenia.
Przyk艂ad (architektura heksagonalna):
// Port TaskService
interface TaskService {
createTask(taskData: TaskData): Promise;
updateTask(taskId: string, taskData: TaskData): Promise;
getTask(taskId: string): Promise;
}
// Adapter klienta API
class ApiTaskService implements TaskService {
async createTask(taskData: TaskData): Promise {
// Wykonaj 偶膮danie API w celu utworzenia zadania
}
async updateTask(taskId: string, taskData: TaskData): Promise {
// Wykonaj 偶膮danie API w celu aktualizacji zadania
}
async getTask(taskId: string): Promise {
// Wykonaj 偶膮danie API w celu pobrania zadania
}
}
// Adapter komponentu React
function TaskList() {
const taskService: TaskService = new ApiTaskService();
const handleCreateTask = async (taskData: TaskData) => {
await taskService.createTask(taskData);
// Zaktualizuj list臋 zada艅
};
// ...
}
Przyk艂ad (czysta architektura):
// Encje
class Task {
constructor(public id: string, public title: string, public description: string) {}
}
// Przypadek u偶ycia
class CreateTaskUseCase {
constructor(private taskGateway: TaskGateway) {}
async execute(title: string, description: string): Promise {
const task = new Task(generateId(), title, description);
await this.taskGateway.create(task);
return task;
}
}
// Adaptery interfejs贸w - Brama (Gateway)
interface TaskGateway {
create(task: Task): Promise;
}
class ApiTaskGateway implements TaskGateway {
async create(task: Task): Promise {
// Wykonaj 偶膮danie API w celu utworzenia zadania
}
}
// Adaptery interfejs贸w - Kontroler
class TaskController {
constructor(private createTaskUseCase: CreateTaskUseCase) {}
async createTask(req: Request, res: Response) {
const { title, description } = req.body;
const task = await this.createTaskUseCase.execute(title, description);
res.json(task);
}
}
// Frameworki i sterowniki - Komponent React
function TaskForm() {
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const apiTaskGateway = new ApiTaskGateway();
const createTaskUseCase = new CreateTaskUseCase(apiTaskGateway);
const taskController = new TaskController(createTaskUseCase);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await taskController.createTask({ body: { title, description } } as Request, { json: (data: any) => console.log(data) } as Response);
};
return (
);
}
4. Zastosuj wstrzykiwanie zale偶no艣ci (Dependency Injection)
Aby jeszcze bardziej oddzieli膰 rdze艅 od system贸w zewn臋trznych, u偶yj wstrzykiwania zale偶no艣ci, aby dostarczy膰 adaptery lub warstwy do rdzenia. Pozwala to na 艂atw膮 wymian臋 r贸偶nych implementacji adapter贸w lub warstw bez modyfikowania kodu rdzenia.
Przyk艂ad:
// Wstrzyknij TaskService do komponentu TaskList
function TaskList(props: { taskService: TaskService }) {
const { taskService } = props;
const handleCreateTask = async (taskData: TaskData) => {
await taskService.createTask(taskData);
// Zaktualizuj list臋 zada艅
};
// ...
}
// U偶ycie
const apiTaskService = new ApiTaskService();
5. Napisz testy jednostkowe
Jedn膮 z kluczowych zalet architektury heksagonalnej i czystej architektury jest lepsza testowalno艣膰. Mo偶esz 艂atwo pisa膰 testy jednostkowe dla kluczowej logiki biznesowej, nie polegaj膮c na zewn臋trznych zale偶no艣ciach. U偶yj atrap adapter贸w lub warstw (mock adapters/layers) do symulacji zachowania system贸w zewn臋trznych i weryfikacji, czy logika rdzenia dzia艂a zgodnie z oczekiwaniami.
Przyk艂ad:
// Atrapa TaskService
class MockTaskService implements TaskService {
async createTask(taskData: TaskData): Promise {
return Promise.resolve({ id: '1', ...taskData });
}
async updateTask(taskId: string, taskData: TaskData): Promise {
return Promise.resolve({ id: taskId, ...taskData });
}
async getTask(taskId: string): Promise {
return Promise.resolve({ id: taskId, title: 'Test Task', description: 'Test Description' });
}
}
// Test jednostkowy
describe('TaskList', () => {
it('should create a task', async () => {
const mockTaskService = new MockTaskService();
const taskList = new TaskList({ taskService: mockTaskService });
const taskData = { title: 'New Task', description: 'New Description' };
const newTask = await taskList.handleCreateTask(taskData);
expect(newTask.title).toBe('New Task');
expect(newTask.description).toBe('New Description');
});
});
Praktyczne uwagi i wyzwania
Chocia偶 architektura heksagonalna i czysta architektura oferuj膮 znaczne korzy艣ci, istniej膮 r贸wnie偶 pewne praktyczne uwagi i wyzwania, o kt贸rych nale偶y pami臋ta膰, stosuj膮c je w tworzeniu frontendu:
- Zwi臋kszona z艂o偶ono艣膰: Te architektury mog膮 zwi臋kszy膰 z艂o偶ono艣膰 bazy kodu, zw艂aszcza w przypadku ma艂ych lub prostych aplikacji.
- Krzywa uczenia si臋: Deweloperzy mog膮 potrzebowa膰 nauczy膰 si臋 nowych koncepcji i wzorc贸w, aby skutecznie wdro偶y膰 te architektury.
- Nadmierna in偶ynieria (Over-engineering): Wa偶ne jest, aby unika膰 nadmiernego komplikowania aplikacji. Zacznij od prostej architektury i stopniowo dodawaj z艂o偶ono艣膰 w miar臋 potrzeb.
- R贸wnowa偶enie abstrakcji: Znalezienie odpowiedniego poziomu abstrakcji mo偶e by膰 wyzwaniem. Zbyt du偶a abstrakcja mo偶e utrudni膰 zrozumienie kodu, podczas gdy zbyt ma艂a mo偶e prowadzi膰 do silnego powi膮zania.
- Kwestie wydajno艣ci: Nadmierne warstwy abstrakcji mog膮 potencjalnie wp艂yn膮膰 na wydajno艣膰. Wa偶ne jest profilowanie aplikacji i identyfikowanie wszelkich w膮skich garde艂 wydajno艣ciowych.
Mi臋dzynarodowe przyk艂ady i adaptacje
Zasady architektury heksagonalnej i czystej architektury maj膮 zastosowanie w tworzeniu frontendu niezale偶nie od lokalizacji geograficznej czy kontekstu kulturowego. Jednak konkretne implementacje i adaptacje mog膮 si臋 r贸偶ni膰 w zale偶no艣ci od wymaga艅 projektu i preferencji zespo艂u deweloperskiego.
Przyk艂ad 1: Globalna platforma e-commerce
Globalna platforma e-commerce mo偶e u偶ywa膰 architektury heksagonalnej do oddzielenia kluczowej logiki zarz膮dzania koszykiem i zam贸wieniami od frameworka UI i bramek p艂atniczych. Rdze艅 by艂by odpowiedzialny za zarz膮dzanie produktami, obliczanie cen i przetwarzanie zam贸wie艅. Adaptery steruj膮ce obejmowa艂yby komponenty React dla katalogu produkt贸w, koszyka i stron kasy. Adaptery sterowane obejmowa艂yby klient贸w API dla r贸偶nych bramek p艂atniczych (np. Stripe, PayPal, Alipay) i dostawc贸w us艂ug wysy艂kowych (np. FedEx, DHL, UPS). Pozwala to platformie 艂atwo dostosowa膰 si臋 do r贸偶nych regionalnych metod p艂atno艣ci i opcji wysy艂ki.
Przyk艂ad 2: Wieloj臋zyczna aplikacja medi贸w spo艂eczno艣ciowych
Wieloj臋zyczna aplikacja medi贸w spo艂eczno艣ciowych mog艂aby u偶ywa膰 czystej architektury do oddzielenia kluczowej logiki uwierzytelniania u偶ytkownik贸w i zarz膮dzania tre艣ci膮 od framework贸w UI i lokalizacji. Encje reprezentowa艂yby u偶ytkownik贸w, posty i komentarze. Przypadki u偶ycia definiowa艂yby, jak u偶ytkownicy tworz膮, udost臋pniaj膮 i wchodz膮 w interakcj臋 z tre艣ci膮. Adaptery interfejs贸w obs艂ugiwa艂yby t艂umaczenie tre艣ci na r贸偶ne j臋zyki i formatowanie danych dla r贸偶nych komponent贸w UI. Pozwala to aplikacji na 艂atwe wspieranie nowych j臋zyk贸w i dostosowywanie si臋 do r贸偶nych preferencji kulturowych.
Podsumowanie
Architektura heksagonalna i czysta architektura dostarczaj膮 cennych zasad do budowy 艂atwych w utrzymaniu, testowalnych i skalowalnych aplikacji frontendowych. Poprzez oddzielenie kluczowej logiki biznesowej od zewn臋trznych zale偶no艣ci, mo偶na stworzy膰 bardziej elastyczn膮 i adaptowaln膮 baz臋 kodu, kt贸r膮 艂atwiej rozwija膰 w czasie. Chocia偶 te architektury mog膮 wprowadza膰 pewn膮 pocz膮tkow膮 z艂o偶ono艣膰, d艂ugoterminowe korzy艣ci w zakresie 艂atwo艣ci utrzymania, testowalno艣ci i skalowalno艣ci sprawiaj膮, 偶e s膮 one wart膮 rozwa偶enia inwestycj膮 w przypadku z艂o偶onych projekt贸w frontendowych. Pami臋taj, aby zacz膮膰 od prostej architektury i stopniowo dodawa膰 z艂o偶ono艣膰 w miar臋 potrzeb, a tak偶e starannie rozwa偶y膰 zwi膮zane z tym praktyczne aspekty i wyzwania.
Przyjmuj膮c te wzorce architektoniczne, deweloperzy frontendowi mog膮 budowa膰 bardziej solidne i niezawodne aplikacje, kt贸re mog膮 sprosta膰 zmieniaj膮cym si臋 potrzebom u偶ytkownik贸w na ca艂ym 艣wiecie.
Dalsza lektura
- Architektura heksagonalna: https://alistaircockburn.com/hexagonal-architecture/
- Czysta architektura: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html